package com.elitesland.security.filter;

import com.elitesland.core.exception.BadJwtTokenException;
import com.elitesland.security.TokenProvider;
import com.elitesland.security.config.bean.JwtProperties;
import com.elitesland.security.handle.JsonAuthenticationEntryPoint;
import com.elitesland.security.service.OnlineUserService;
import com.elitesland.security.service.entity.OnlineUserDO;
import io.jsonwebtoken.ExpiredJwtException;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.filter.GenericFilterBean;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Optional;

/**
 * <pre>
 * [功能说明]
 * </pre>
 *
 * @author Mir
 * @date 2020/6/16
 */
@Slf4j
public class JwtTokenFilter extends GenericFilterBean {

    private final JwtProperties jwtProperties;

    private final OnlineUserService onlineUserService;

    private final TokenProvider tokenProvider;

    private final AuthenticationEntryPoint authenticationEntryPoint = new JsonAuthenticationEntryPoint();

    private final AntPathRequestMatcher currentMatcher = new AntPathRequestMatcher("/sys/users/current", "GET");

    public JwtTokenFilter(JwtProperties jwtProperties, OnlineUserService onlineUserService, TokenProvider tokenProvider) {
        this.jwtProperties = jwtProperties;
        this.onlineUserService = onlineUserService;
        this.tokenProvider = tokenProvider;
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        logger.debug("JWT token filter triggerred");

        val httpServletRequest = (HttpServletRequest) servletRequest;

        val token = resolveToken(httpServletRequest);

        if (token.isEmpty() && currentMatcher.matches(httpServletRequest)) {
            throw new BadJwtTokenException("获取当前用户信息需提供令牌");
        }
        if (token.isPresent()) {
            val t = token.get();

            OnlineUserDO onlineUserDO = null;
            try {
                onlineUserDO = onlineUserService.getOne(jwtProperties.getOnlineKey() + t);
            } catch (ExpiredJwtException e) {
                log.error(e.getMessage());
            }
            if (onlineUserDO != null && org.springframework.util.StringUtils.hasText(t)) {
                Authentication authentication = tokenProvider.getAuthentication(t);
                SecurityContextHolder.getContext().setAuthentication(authentication);
                // Token 续期
                tokenProvider.checkRenewal(t);

                filterChain.doFilter(servletRequest, servletResponse);
            } else {
                authenticationEntryPoint.commence(
                        (HttpServletRequest) servletRequest,
                        (HttpServletResponse) servletResponse,
                        new BadJwtTokenException("认证令牌已过期"));
            }
        } else {
            //todo: 放开token验证，便于调试，正式环境需要关闭
            filterChain.doFilter(servletRequest, servletResponse);
        }
    }

    private Optional<String> resolveToken(HttpServletRequest request) throws BadJwtTokenException {
        val bearerToken = request.getHeader(jwtProperties.getHeader());
        if (StringUtils.isNotBlank(bearerToken) && bearerToken.startsWith(jwtProperties.getTokenStartWith())) {
            return Optional.of(bearerToken.replace(jwtProperties.getTokenStartWith(), ""));
        } else {
            log.debug("Illegal token: {}", bearerToken);
//            throw new BadJwtTokenException("非法JWT令牌");
        }
        return Optional.empty();
    }
}
